
//--xuezhen--

`include "defines.v"

module exe_stage(
    input wire              rst,

    input wire [63: 0]      inst_addr_i,
    input wire [31: 0]      inst_i,

    input wire              rd_w_ena_i,
    input wire [4 : 0]      rd_w_addr_i,
    input wire              csr_w_ena_i,
    input wire [11: 0]      csr_w_addr_i,    
    input wire [`REG_BUS]   op1_i,
    input wire [`REG_BUS]   op2_i,
    input wire [`REG_BUS]   op3_i,
    
    input wire[`ALU_SEL_BUS]    alu_sel_i,
    input wire                  alu_sub_flag_i,
    input wire                  alu_word_flag_i,
    input wire                  alu_symbol_flag_i,

    input wire                    transfer_en_i,
    input wire[`TRANSFER_SEL_BUS] transfer_sel_i,

    input wire                  csr_en_i,
    input wire[`CSR_SEL_BUS]    csr_sel_i,

    input wire                  mem_load_en_i,
    input wire                  mem_store_en_i,
    input wire[`MEM_SEL_BUS]    mem_sel_i,

    input wire[`RD_SEL_BUS]     rd_sel_i,

    input wire [`REG_BUS]   exception_type_from_mem_i,
    input wire [`REG_BUS]   exception_type_i,
    input wire              valid_i,

    output wire [63: 0]     inst_addr_o,
    output wire [31: 0]     inst_o,

    output wire             rd_w_ena_o,
    output wire [4 : 0]     rd_w_addr_o,
    output wire [`REG_BUS]  rd_w_data_o,

    output wire             csr_w_ena_o,
    output wire [11: 0]     csr_w_addr_o,
    output wire [`REG_BUS]  csr_w_data_o,

    output wire [`REG_BUS]  mem_addr_o,
    output wire [`REG_BUS]  mem_w_data_o,
    
    output wire             transfer_flag_o,
    output wire [`REG_BUS]  transfer_address_o,

    output wire                  mem_load_en_o,
    output wire                  mem_store_en_o,
    output wire[`MEM_SEL_BUS]    mem_sel_o,

    output wire[`RD_SEL_BUS]     rd_sel_o,

    output wire [`REG_BUS]  exception_type_o,
    output wire             valid_o
);
    assign inst_addr_o   = inst_addr_i;
    assign inst_o        = inst_i;

    assign rd_w_ena_o  = (rst == 1'h0) ? rd_w_ena_i : 1'b0;
    assign rd_w_addr_o  = rd_w_addr_i;

    assign csr_w_ena_o = csr_w_ena_i;
    assign csr_w_addr_o = csr_w_addr_i;

    assign mem_load_en_o  = mem_load_en_i;
    assign mem_store_en_o = mem_store_en_i;
    assign mem_sel_o      = mem_sel_i;

    assign rd_sel_o       = rd_sel_i;

    assign exception_type_o = exception_type_i;
    assign valid_o = valid_i;

    //exception
    wire freeze_flag;
    assign freeze_flag = exception_type_o == `ZERO_WORD && exception_type_from_mem_i == `ZERO_WORD ? 1'b0 : 1'b1;
    //ALU
    /**********************************
    *logic
    ***********************************/
    wire[`REG_BUS] logic_or_result;
    wire[`REG_BUS] logic_and_result;
    wire[`REG_BUS] logic_xor_result;
    wire[`REG_BUS] logic_result;

    assign logic_or_result  = op1_i | op2_i;
    assign logic_and_result = op1_i & op2_i;
    assign logic_xor_result = op1_i ^ op2_i;

    /**********************************
    *shift
    ***********************************/

    wire[`REG_BUS] shift_sll_dw_result;
    wire[`REG_BUS] shift_srl_dw_result;
    wire[`REG_BUS] shift_sra_dw_result;
    
    wire [`REG_BUS]shift_dw_src;
    wire [`REG_BUS]shift_dw_result_pre;
    wire [`REG_BUS]shift_dw_mask;

    wire[`REG_BUS] shift_sll_w_result;
    wire[`REG_BUS] shift_srl_w_result;
    wire[`REG_BUS] shift_sra_w_result;
    wire[31: 0] shift_sra_w_result_pre;

    wire [31: 0]shift_w_src;
    wire [31: 0]shift_w_result_pre;
    wire [31: 0]shift_w_mask;
    
    wire[`REG_BUS] shift_sll_result;
    wire[`REG_BUS] shift_srl_result;
    wire[`REG_BUS] shift_sra_result;

    assign shift_dw_src = (alu_sel_i == `ALU_SEL_SLL) ? {
                                                            op1_i[ 0], op1_i[ 1], op1_i[ 2], op1_i[ 3], op1_i[ 4], op1_i[ 5], op1_i[ 6], op1_i[ 7], 
                                                            op1_i[ 8], op1_i[ 9], op1_i[10], op1_i[11], op1_i[12], op1_i[13], op1_i[14], op1_i[15], 
                                                            op1_i[16], op1_i[17], op1_i[18], op1_i[19], op1_i[20], op1_i[21], op1_i[22], op1_i[23], 
                                                            op1_i[24], op1_i[25], op1_i[26], op1_i[27], op1_i[28], op1_i[29], op1_i[30], op1_i[31], 
                                                            op1_i[32], op1_i[33], op1_i[34], op1_i[35], op1_i[36], op1_i[37], op1_i[38], op1_i[39], 
                                                            op1_i[40], op1_i[41], op1_i[42], op1_i[43], op1_i[44], op1_i[45], op1_i[46], op1_i[47], 
                                                            op1_i[48], op1_i[49], op1_i[50], op1_i[51], op1_i[52], op1_i[53], op1_i[54], op1_i[55], 
                                                            op1_i[56], op1_i[57], op1_i[58], op1_i[59], op1_i[60], op1_i[61], op1_i[62], op1_i[63]
                                                        }
                                                      : op1_i;
    assign shift_dw_result_pre = shift_dw_src >> op2_i[5:0];
    assign shift_dw_mask = ~(64'hffffffff_ffffffff >> op2_i[5:0]);
    assign shift_sll_dw_result = {
                                    shift_dw_result_pre[ 0], shift_dw_result_pre[ 1], shift_dw_result_pre[ 2], shift_dw_result_pre[ 3], shift_dw_result_pre[ 4], shift_dw_result_pre[ 5], shift_dw_result_pre[ 6], shift_dw_result_pre[ 7], 
                                    shift_dw_result_pre[ 8], shift_dw_result_pre[ 9], shift_dw_result_pre[10], shift_dw_result_pre[11], shift_dw_result_pre[12], shift_dw_result_pre[13], shift_dw_result_pre[14], shift_dw_result_pre[15], 
                                    shift_dw_result_pre[16], shift_dw_result_pre[17], shift_dw_result_pre[18], shift_dw_result_pre[19], shift_dw_result_pre[20], shift_dw_result_pre[21], shift_dw_result_pre[22], shift_dw_result_pre[23], 
                                    shift_dw_result_pre[24], shift_dw_result_pre[25], shift_dw_result_pre[26], shift_dw_result_pre[27], shift_dw_result_pre[28], shift_dw_result_pre[29], shift_dw_result_pre[30], shift_dw_result_pre[31], 
                                    shift_dw_result_pre[32], shift_dw_result_pre[33], shift_dw_result_pre[34], shift_dw_result_pre[35], shift_dw_result_pre[36], shift_dw_result_pre[37], shift_dw_result_pre[38], shift_dw_result_pre[39], 
                                    shift_dw_result_pre[40], shift_dw_result_pre[41], shift_dw_result_pre[42], shift_dw_result_pre[43], shift_dw_result_pre[44], shift_dw_result_pre[45], shift_dw_result_pre[46], shift_dw_result_pre[47], 
                                    shift_dw_result_pre[48], shift_dw_result_pre[49], shift_dw_result_pre[50], shift_dw_result_pre[51], shift_dw_result_pre[52], shift_dw_result_pre[53], shift_dw_result_pre[54], shift_dw_result_pre[55], 
                                    shift_dw_result_pre[56], shift_dw_result_pre[57], shift_dw_result_pre[58], shift_dw_result_pre[59], shift_dw_result_pre[60], shift_dw_result_pre[61], shift_dw_result_pre[62], shift_dw_result_pre[63]
                                };
                                                           
    assign shift_srl_dw_result = shift_dw_result_pre;
    assign shift_sra_dw_result = ({64{op1_i[63]}} & shift_dw_mask | shift_dw_result_pre);

    assign shift_w_src = (alu_sel_i == `ALU_SEL_SLL) ? {
                                                            op1_i[ 0], op1_i[ 1], op1_i[ 2], op1_i[ 3], op1_i[ 4], op1_i[ 5], op1_i[ 6], op1_i[ 7], 
                                                            op1_i[ 8], op1_i[ 9], op1_i[10], op1_i[11], op1_i[12], op1_i[13], op1_i[14], op1_i[15], 
                                                            op1_i[16], op1_i[17], op1_i[18], op1_i[19], op1_i[20], op1_i[21], op1_i[22], op1_i[23], 
                                                            op1_i[24], op1_i[25], op1_i[26], op1_i[27], op1_i[28], op1_i[29], op1_i[30], op1_i[31]
                                                       }
                                                       : op1_i[31: 0];
    assign shift_w_result_pre = shift_w_src >> op2_i[4:0];
    assign shift_w_mask = ~(32'hffffffff >> op2_i[4:0]);
    assign shift_sll_w_result = {
                                    {32{shift_w_result_pre[ 0]}},
                                    shift_w_result_pre[ 0], shift_w_result_pre[ 1], shift_w_result_pre[ 2], shift_w_result_pre[ 3], shift_w_result_pre[ 4], shift_w_result_pre[ 5], shift_w_result_pre[ 6], shift_w_result_pre[ 7], 
                                    shift_w_result_pre[ 8], shift_w_result_pre[ 9], shift_w_result_pre[10], shift_w_result_pre[11], shift_w_result_pre[12], shift_w_result_pre[13], shift_w_result_pre[14], shift_w_result_pre[15], 
                                    shift_w_result_pre[16], shift_w_result_pre[17], shift_w_result_pre[18], shift_w_result_pre[19], shift_w_result_pre[20], shift_w_result_pre[21], shift_w_result_pre[22], shift_w_result_pre[23], 
                                    shift_w_result_pre[24], shift_w_result_pre[25], shift_w_result_pre[26], shift_w_result_pre[27], shift_w_result_pre[28], shift_w_result_pre[29], shift_w_result_pre[30], shift_w_result_pre[31]
                                };
    assign shift_srl_w_result = {{32{shift_w_result_pre[31]}}, shift_w_result_pre};
    assign shift_sra_w_result_pre = ({32{op1_i[31]}} & shift_w_mask | shift_w_result_pre);
    assign shift_sra_w_result = {{32{shift_sra_w_result_pre[31]}}, shift_sra_w_result_pre};

    assign shift_sll_result = alu_word_flag_i == 1'b1 ? shift_sll_w_result : shift_sll_dw_result;
    assign shift_srl_result = alu_word_flag_i == 1'b1 ? shift_srl_w_result : shift_srl_dw_result;
    assign shift_sra_result = alu_word_flag_i == 1'b1 ? shift_sra_w_result : shift_sra_dw_result;
    /****************************
    *add
    *****************************/
    wire[`REG_BUS] operand2_i_mux;
    wire[`REG_BUS] result_sum;
    // wire           overflow_sum;

    wire operand1_lt_operand2;

    wire[`REG_BUS] arithmetic_slt_result;
    wire[`REG_BUS] arithmetic_add_result;

    wire[`REG_BUS] arithmetic_result;

    assign operand2_i_mux       = alu_sub_flag_i == 1'b1 ? (~op2_i) + 1 : op2_i; 
    assign result_sum           = op1_i + operand2_i_mux;
    // assign overflow_sum         = ((~op1_i[31] & ~operand2_i_mux[31]) &   result_sum[31]) |
    //                               (( op1_i[31] &  operand2_i_mux[31]) & (~result_sum[31]));
    //如果是有符号数则特殊
    //如果是无符号数或者符号相同则直接比较
    assign operand1_lt_operand2 = alu_symbol_flag_i ? ( ( op1_i[63] & ~op2_i[63])
                                                    | (~op1_i[63] & ~op2_i[63] & result_sum[63])
                                                    | ( op1_i[63] &  op2_i[63] & result_sum[63])
                                                    ) 
                                                  : (op1_i < op2_i);

    assign arithmetic_slt_result = {63'h0, operand1_lt_operand2};

    assign arithmetic_add_result = alu_word_flag_i == 1'b1 ? {{32{result_sum[31]}}, result_sum[31: 0]} : result_sum;

    reg[`REG_BUS] alu_result;
    always @(*) begin
        case(alu_sel_i)
            `ALU_SEL_OR:  begin
                alu_result = logic_or_result;
            end
            `ALU_SEL_AND: begin
                alu_result = logic_and_result;
            end
            `ALU_SEL_XOR: begin
                alu_result = logic_xor_result;
            end
            `ALU_SEL_SLL: begin
                alu_result = shift_sll_result;
            end
            `ALU_SEL_SRL: begin
                alu_result = shift_srl_result;
            end
            `ALU_SEL_SRA: begin
                alu_result = shift_sra_result;
            end
            `ALU_SEL_SLT: begin
                alu_result = arithmetic_slt_result;
            end
            `ALU_SEL_ADD: begin
                alu_result = arithmetic_add_result;
            end
            `ALU_SEL_SUB: begin
                alu_result = arithmetic_add_result;
            end
            default: begin
                alu_result = `ZERO_WORD;
            end
        endcase
    end
    /**********************************
    *加载存储指令
    ***********************************/
    
    assign mem_addr_o = result_sum;

    assign mem_w_data_o = op3_i;

    /**********************************
    *转移指令
    ***********************************/
    
    //branch
    
    wire[`REG_BUS] branch_address;

    assign branch_address = inst_addr_i + op3_i;

    wire inst_beq_flag ;
    wire inst_bne_flag ;
    wire inst_blt_flag ;
    wire inst_bge_flag ;
    wire inst_bltu_flag;
    wire inst_bgeu_flag;

    assign inst_beq_flag  = (transfer_en_i == 1'b1) && (transfer_sel_i == `TRANSFER_BEQ ) ? 1'h1 : 1'h0;
    assign inst_bne_flag  = (transfer_en_i == 1'b1) && (transfer_sel_i == `TRANSFER_BNE ) ? 1'h1 : 1'h0;
    assign inst_blt_flag  = (transfer_en_i == 1'b1) && (transfer_sel_i == `TRANSFER_BLT ) ? 1'h1 : 1'h0;
    assign inst_bge_flag  = (transfer_en_i == 1'b1) && (transfer_sel_i == `TRANSFER_BGE ) ? 1'h1 : 1'h0;
    assign inst_bltu_flag = (transfer_en_i == 1'b1) && (transfer_sel_i == `TRANSFER_BLTU) ? 1'h1 : 1'h0;
    assign inst_bgeu_flag = (transfer_en_i == 1'b1) && (transfer_sel_i == `TRANSFER_BGEU) ? 1'h1 : 1'h0;
    
    //jump and link
    wire inst_jal_flag;
    wire inst_jalr_flag;
    wire[`REG_BUS] pc_plus_4;

    assign pc_plus_4 = inst_addr_i + 64'h4;

    assign inst_jal_flag  = (transfer_en_i == 1'b1) && (transfer_sel_i == `TRANSFER_JAL ) ? 1'h1 : 1'h0;
    assign inst_jalr_flag = (transfer_en_i == 1'b1) && (transfer_sel_i == `TRANSFER_JALR) ? 1'h1 : 1'h0;
    
    wire[`REG_BUS] link_result;
    assign link_result = inst_jal_flag | inst_jalr_flag ? pc_plus_4 : `ZERO_WORD;

    assign transfer_flag_o = rst         ? 1'b0 :
                             freeze_flag ? 1'b0 : (  inst_jal_flag
                                                  || inst_jalr_flag
                                                  || inst_beq_flag  && (op1_i == op2_i)              
                                                  || inst_bne_flag  && (op1_i != op2_i)              
                                                  || inst_blt_flag  && (operand1_lt_operand2 == 1'h1)
                                                  || inst_bge_flag  && (operand1_lt_operand2 == 1'h0)
                                                  || inst_bltu_flag && (operand1_lt_operand2 == 1'h1)
                                                  || inst_bgeu_flag && (operand1_lt_operand2 == 1'h0) );

    assign transfer_address_o = (inst_jalr_flag  == 1'b1) ? result_sum     :
                                (transfer_flag_o == 1'b1) ? branch_address : `ZERO_WORD;
    /**********************************
    *CSR
    ***********************************/
    wire [`REG_BUS] csr_result;
    assign csr_result = op1_i;
    assign csr_w_data_o = (csr_sel_i == `CSR_CSRRW) ? op2_i          :
                          (csr_sel_i == `CSR_CSRRS) ? op1_i | op2_i  :
                          (csr_sel_i == `CSR_CSRRC) ? op1_i & ~op2_i : `ZERO_WORD;

/////////////////////////////////////////////////////////////////////////////
    assign rd_w_data_o  = rd_sel_i == `RD_SEL_ALU  ? alu_result :
                          rd_sel_i == `RD_SEL_LINK ? link_result:
                          rd_sel_i == `RD_SEL_CSR  ? csr_result : 0;

endmodule
